home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Resources / Browsers, Managers & Extensions / Mozilla Weave 0.2.7 / latest-weave.xpi / modules / xmpp / transportLayer.js < prev    next >
Text File  |  2008-07-08  |  14KB  |  416 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Bookmarks Sync.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla.
  17.  * Portions created by the Initial Developer are Copyright (C) 2008
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Jono DiCarlo <jdicarlo@mozilla.org>
  22.  *
  23.  * Alternatively, the contents of this file may be used under the terms of
  24.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  25.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  26.  * in which case the provisions of the GPL or the LGPL are applicable instead
  27.  * of those above. If you wish to allow use of your version of this file only
  28.  * under the terms of either the GPL or the LGPL, and not to allow others to
  29.  * use your version of this file under the terms of the MPL, indicate your
  30.  * decision by deleting the provisions above and replace them with the notice
  31.  * and other provisions required by the GPL or the LGPL. If you do not delete
  32.  * the provisions above, a recipient may use your version of this file under
  33.  * the terms of any one of the MPL, the GPL or the LGPL.
  34.  *
  35.  * ***** END LICENSE BLOCK ***** */
  36.  
  37. const EXPORTED_SYMBOLS = ['HTTPPollingTransport'];
  38.  
  39. var Cc = Components.classes;
  40. var Ci = Components.interfaces;
  41. var Cu = Components.utils;
  42.  
  43. Cu.import("resource://weave/log4moz.js");
  44.  
  45. /* 
  46.   The interface that should be implemented by any Transport object: 
  47.  
  48.   send( messageXml );
  49.   setCallbackObject(object with .onIncomingData(aStringData) and .onTransportError(aErrorText) );
  50.   connect();
  51.   disconnect();
  52. */
  53.  
  54. function InputStreamBuffer() {
  55. }
  56. InputStreamBuffer.prototype = {
  57.   _data: "",
  58.   append: function( stuff ) {
  59.     this._data = this._data + stuff;
  60.   },
  61.   clear: function() {
  62.     this._data = "";
  63.   },
  64.   getData: function() {
  65.     return this._data;
  66.   }
  67. }
  68.  
  69. /**
  70.  * A transport layer that uses raw sockets.
  71.  * Not recommended for use; currently fails when trying to negotiate
  72.  * TLS.
  73.  * Use HTTPPollingTransport instead.
  74.  */
  75. function SocketClient( host, port ) {
  76.   this._init( host, port );
  77. }
  78. SocketClient.prototype = {
  79.   __threadManager: null,
  80.   get _threadManager() {
  81.     if (!this.__threadManager)
  82.       this.__threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
  83.     return this.__threadManager;
  84.   },
  85.  
  86.   __transport: null,
  87.   get _transport() {
  88.     if (!this.__transport) {
  89.       var transportService = Cc["@mozilla.org/network/socket-transport-service;1"].getService(Ci.nsISocketTransportService);
  90.       this.__transport = transportService.createTransport(['starttls'],
  91.                                1,    // ssl only
  92.                                this._host,
  93.                                this._port,
  94.                                null); // proxy
  95.     }
  96.     return this.__transport;
  97.   },
  98.  
  99.   _init: function( host, port ) {
  100.     this._host = host;
  101.     this._port = port;
  102.     this._contentRead = "";
  103.     this._buffer = null;
  104.     this.connect();
  105.   },
  106.  
  107.   connect: function() {
  108.     var outstream = this._transport.openOutputStream( 0,  // flags
  109.                               0,  // buffer size
  110.                               0 ); // number of buffers
  111.     this._outstream = outstream;
  112.  
  113.     var buffer = new InputStreamBuffer;
  114.     this._buffer = buffer;
  115.  
  116.     // Wrap input stream is C only, nonscriptable, so wrap it in scriptable
  117.     // stream:
  118.     var rawInputStream = this._transport.openInputStream( 0, 0, 0 );
  119.     var scriptablestream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
  120.     scriptablestream.init(rawInputStream);
  121.  
  122.     // input stream pump for asynchronous reading
  123.     var pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(Ci.nsIInputStreamPump);
  124.     pump.init(rawInputStream, -1, -1, 0, 0, 
  125.           false); //automatically close once all data read?
  126.  
  127.     // create dataListener class for callback:
  128.     var dataListener = {
  129.       data : "",
  130.       onStartRequest: function(request, context){
  131.       },
  132.       onStopRequest: function(request, context, status){
  133.           rawInputStream.close();
  134.           outstream.close();
  135.       },
  136.       onDataAvailable: function(request, context, inputStream, offset, count){
  137.         // use scriptable stream wrapper, not "real" stream.
  138.         // count is number of bytes available, offset is position in stream.
  139.         // Do stuff with data here!
  140.         buffer.append( scriptablestream.read( count ));
  141.       }
  142.     };
  143.     // register it:
  144.     pump.asyncRead(dataListener, null); // second argument is a context
  145.     //which gets passed in as the context argument to methods of dataListener
  146.  
  147.     //Should be done connecting now.  TODO: Catch and report errors.
  148.   },
  149.  
  150.   send: function( messageText ) {
  151.     this._outstream.write( messageText, messageText.length );
  152.   },
  153.  
  154.   getBinaryOutStream: function() {
  155.     var binaryOutStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(Ci.nsIBinaryOutputStream);
  156.     binaryOutStream.setOutputStream( this._outstream ); // is this right?
  157.     return binaryOutStream;
  158.   },
  159.  
  160.   disconnect: function() {
  161.     var thread = this._threadManager.currentThread;
  162.     while ( thread.hasPendingEvents() ) {
  163.         thread.processNextEvent( true );
  164.     }
  165.   },
  166.  
  167.   checkResponse: function() {
  168.     return this._getData();
  169.   },
  170.  
  171.   waitForResponse: function() {
  172.     var thread = this._threadManager.currentThread;
  173.     while( this._buffer.getData().length == 0 ) {
  174.         thread.processNextEvent( true );
  175.     }
  176.     var output = this._buffer.getData();
  177.     this._buffer.clear();
  178.     return output;
  179.   },
  180.  
  181.   startTLS: function() {
  182.     this._transport.securityInfo.QueryInterface(Ci.nsISSLSocketControl);
  183.     this._transport.securityInfo.StartTLS();
  184.   }
  185. };
  186.  
  187.  
  188. /**
  189.  * Send HTTP requests periodically to the server using a timer.
  190.  * HTTP POST requests with content-type application/x-www-form-urlencoded.
  191.  * responses from the server have content-type text/xml
  192.  * request and response are UTF-8 encoded (ignore what HTTP header says)
  193.  * identify session by always using set-cookie header with cookie named ID
  194.  * first request sets this to 0 to indicate new session.
  195.  */
  196. function HTTPPollingTransport( serverUrl, useKeys, interval ) {
  197.   this._init( serverUrl, useKeys, interval );
  198. }
  199. HTTPPollingTransport.prototype = {
  200.   _init: function( serverUrl, useKeys, interval ) {
  201.     this._log = Log4Moz.Service.getLogger("Service.XmppTransportLayer");
  202.     this._log.info("Initializing transport: serverUrl=" + serverUrl + ", useKeys=" + useKeys + ", interval=" + interval);
  203.     this._serverUrl = serverUrl
  204.     this._n = 0;
  205.     this._key = this._makeSeed();
  206.     this._latestCookie = "";
  207.     this._connectionId = 0;
  208.     this._callbackObject = null;
  209.     this._useKeys = useKeys;
  210.     this._interval = interval;
  211.     this._outgoingRetryBuffer = "";
  212.     this._retryCount = 0;
  213.     this._retryCap = 0;
  214.   },
  215.  
  216.   get _request() {
  217.     let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance( Ci.nsIXMLHttpRequest );
  218.     request.mozBackgroundRequest = true;
  219.     this.__defineGetter__("_request", function() request);
  220.     return this._request;
  221.   },
  222.  
  223.   __hasher: null,
  224.   get _hasher() {
  225.     if (!this.__hasher)
  226.       this.__hasher = Cc["@mozilla.org/security/hash;1"].createInstance( Ci.nsICryptoHash );
  227.     return this.__hasher;
  228.   },
  229.  
  230.   __timer: null,
  231.   get _timer() {
  232.     if (!this.__timer)
  233.       this.__timer = Cc["@mozilla.org/timer;1"].createInstance( Ci.nsITimer );
  234.     return this.__timer;
  235.   },
  236.  
  237.   _makeSeed: function() {
  238.     return "foo";//"MyKeyOfHorrors";
  239.   },
  240.  
  241.   _advanceKey: function() {
  242.     var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  243.                     createInstance(Ci.nsIScriptableUnicodeConverter);
  244.  
  245.     // we use UTF-8 here, you can choose other encodings.
  246.     // TODO make configurable
  247.     converter.charset = "UTF-8";
  248.     // result is an out parameter,
  249.     // result.value will contain the array length
  250.     var result = {};
  251.     // data is an array of bytes
  252.     var data = converter.convertToByteArray(this._key, result);
  253.  
  254.     this._n += 1;
  255.     this._hasher.initWithString( "SHA1" );
  256.     this._hasher.update( data, data.length );
  257.     this._key = this._hasher.finish( true ); // true means B64encode
  258.   },
  259.  
  260.   _setIdFromCookie: function( self, cookie ) {
  261.     // parse connection ID out of the cookie:
  262.     var cookieSegments = cookie.split( ";" );
  263.     cookieSegments = cookieSegments[0].split( "=" );
  264.     var newConnectionId = cookieSegments[1];
  265.     switch( newConnectionId) {
  266.     case "0:0":
  267.       self._onError( "Unknown error!\n" );
  268.       break;
  269.     case "-1:0":
  270.       self._onError( "Server error!\n" );
  271.       break;
  272.     case "-2:0":
  273.       self._onError( "Bad request!\n" );
  274.       break;
  275.     case "-3:0":
  276.       self._onError( "Key sequence error!\n" );
  277.       break;
  278.     default :
  279.       self._connectionId = cookieSegments[1];
  280.       this._log.debug("Connection ID set to " + self._connectionId);
  281.       break;
  282.     }
  283.   },
  284.  
  285.   _onError: function( errorText ) {
  286.     this._log.error( errorText );
  287.     if ( this._callbackObject != null ) {
  288.       this._callbackObject.onTransportError( errorText );
  289.     }
  290.     this.disconnect();
  291.   },
  292.  
  293.   _doPost: function( requestXml ) {
  294.     var request = this._request;
  295.     var callbackObj = this._callbackObject;
  296.     var self = this;
  297.     var contents = "";
  298.  
  299.     if ( this._useKey ) {
  300.       this._advanceKey();
  301.       contents = this._connectionId + ";" + this._key + "," + requestXml;
  302.     } else {
  303.       contents = this._connectionId + "," + requestXml;
  304.       /* TODO:
  305.         Currently I get a "-3:0" error (key sequence error) from the 2nd
  306.         exchange if using the keys is enabled. */
  307.     }    
  308.  
  309.     var _processReqChange = function() {
  310.       // Callback for XMLHTTPRequest object state change messages
  311.       if ( request.readyState == 4 ) {
  312.         if ( request.status == 200) {
  313.           // 200 means success.
  314.           
  315.           self._log.debug("Server says: " + request.responseText);
  316.           // Look for a set-cookie header:
  317.           var latestCookie = request.getResponseHeader( "Set-Cookie" );
  318.           if ( latestCookie.length > 0 ) {
  319.             self._setIdFromCookie( self, latestCookie );
  320.           }
  321.  
  322.           // Respond to any text we get back from the server in response
  323.           if ( callbackObj != null && request.responseText.length > 0 ) {
  324.             callbackObj.onIncomingData( request.responseText );
  325.           }
  326.         } else {
  327.           self._log.error( "Got HTTP status code " + request.status );
  328.           if ( request.status == 0 ) {
  329.             /* Sometimes the server gives us HTTP status code 0 in response
  330.                to an attempt to POST. I'm not sure why this happens, but
  331.                if we re-send the POST it seems to usually work the second
  332.                time.  So put the message into a buffer and try again later:
  333.             */
  334.             if (self._retryCount >= self._retryCap) {
  335.               self._onError("Maximum number of retries reached. Unable to communicate with the server.");
  336.             }
  337.             else {
  338.               self._outgoingRetryBuffer = requestXml;
  339.               self._retryCount++;
  340.             }
  341.           }
  342.           else if (request.status == 404) {
  343.             self._onError("Provided URL is not valid.");
  344.           }
  345.           else {
  346.             self._onError("Unable to communicate with the server.");
  347.           }
  348.         }
  349.       }
  350.     };
  351.  
  352.     try {
  353.       request.open( "POST", this._serverUrl, true ); //async = true
  354.       request.setRequestHeader( "Content-type", "application/x-www-form-urlencoded;charset=UTF-8" );
  355.       request.setRequestHeader( "Content-length", contents.length );
  356.       request.setRequestHeader( "Connection", "close" );
  357.       request.onreadystatechange = _processReqChange;
  358.       this._log.debug("Sending: " + contents);
  359.       request.send( contents );
  360.     } catch(ex) { 
  361.       this._onError("Unable to send message to server: " + this._serverUrl);
  362.       this._log.error("Connection failure: " + ex);
  363.     }
  364.   },
  365.  
  366.   send: function( messageXml ) {
  367.     this._doPost( messageXml );
  368.   },
  369.  
  370.   setCallbackObject: function( callbackObject ) {
  371.     this._callbackObject = callbackObject;
  372.   },
  373.  
  374.   notify: function( timer ) {
  375.     /* having a notify method makes this object satisfy the nsITimerCallback
  376.        interface, so the object can be passed to timer.initWithCallback. */
  377.  
  378.     /* With HTTPPolling, we need to be contacting the server on a regular
  379.        heartbeat, even if we don't have anything to send, just to see if
  380.        the server has any data it's waiting to give us.
  381.        If we have a message waiting in the outgoingRetryBuffer, send that;
  382.        otherwise send nothing. */
  383.     var outgoingMsg = this._outgoingRetryBuffer
  384.     this._outgoingRetryBuffer = "";
  385.     this._doPost( outgoingMsg );
  386.   },
  387.  
  388.   connect: function() {
  389.     // In case this is a reconnect, make sure to re-initialize.
  390.     this._init(this._serverUrl, this._useKeys, this._interval);
  391.  
  392.     /* Set up a timer to poll the server periodically. */
  393.  
  394.     // TODO doPost isn't reentrant; don't try to doPost if there's
  395.     //already a post in progress... or can that never happen?
  396.  
  397.     this._timer.initWithCallback( this, 
  398.         this._interval, 
  399.         this._timer.TYPE_REPEATING_SLACK );
  400.   },
  401.  
  402.   disconnect: function () {
  403.     this._request.abort();
  404.     this._timer.cancel();
  405.   },
  406.  
  407.   testKeys: function () {
  408.     this._key = "foo";
  409.     this._log.debug(this._key);
  410.     for ( var x = 1; x < 7; x++ ) {
  411.       this._advanceKey();
  412.       this._log.debug(this._key);
  413.     }
  414.   }
  415. };
  416.